package com.flame.core.utils.cipher;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

/**
 * Sm2Cipher
 *
 * @author wuxintong
 * @since 2023/1/28
 */
public class Sm2Cipher implements FlameCipher {

    private static final String PUBLIC_KEY = "03a9a61546af3a887b815d277c61b9e6d26cabe40a93268aac7873f1bfa9e2357a";

    private static final String PRIVATE_KEY = "79810af53a7b017880a8eddf227346585c60b645d1c0578848278c62956448df";


    public Map<String, String> generateKey() {
        Map<String, String> map = new HashMap<>();
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");

            keyPairGenerator.initialize(new ECGenParameterSpec("sm2p256v1"));
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
            BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
            // true 压缩公钥，false 未压缩公钥
            map.put("publicKey", Hex.toHexString(publicKey.getQ().getEncoded(true)));
            map.put("privateKey", Hex.toHexString(privateKey.getD().toByteArray()));
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
            e.printStackTrace();
        }
        return map;
    }

    @Override
    public String encrypt(String data) throws Exception {
        return encrypt(data, PUBLIC_KEY);
    }

    @Override
    public String decrypt(String data) throws Exception {
        return decrypt(data, PRIVATE_KEY);
    }

    @Override
    public String encrypt(String data, String publicKey) throws Exception {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters =
            new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
        byte[] res = sm2Engine.processBlock(data.getBytes(), 0, data.getBytes().length);
        return Hex.toHexString(res);
    }

    @Override
    public String decrypt(String data, String privateKey) throws Exception {
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters =
            new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);

        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!data.startsWith("04")) {
            data = "04" + data;
        }
        byte[] dataByte = Hex.decode(data);
        byte[] res = sm2Engine.processBlock(dataByte, 0, dataByte.length);
        return new String(res);
    }
}
